package com.smartrodin.springbootrabbitmq.aop;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.smartrodin.springbootrabbitmq.annotation.LocalLock;
import com.smartrodin.springbootrabbitmq.util.IpUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

/**
 * @author: arnold_nie
 * @date: 2021/1/7 15:52
 * FileName: LocalLockApo
 * Description:  重复提交原型 aop
 */
@Aspect
@Component
public class LocalLockApo {

    /**
     * 单机版使用内存做缓存
     */
    private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder()
            // 最大缓存 1000 个
            .maximumSize(1000)
            // 设置写缓存后 5 秒钟过期
            .expireAfterWrite(5, TimeUnit.SECONDS)
            .build();

    /**
     * 分布式使用 redis 缓存
     */
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private HttpServletRequest httpServletRequest;

    @Around("@annotation(com.smartrodin.springbootrabbitmq.annotation.LocalLock)")
    public Object interceptor(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        LocalLock localLock = method.getAnnotation( LocalLock.class);
        String key = getKey(localLock.key(), pjp.getArgs());
        if (!StringUtils.isEmpty(key)) {
            /*// 单机版
            if (CACHES.getIfPresent(key) != null) {
                throw new RuntimeException("请勿重复请求");
            }
            // 如果是第一次请求,就将 key 当前对象压入缓存中
            CACHES.put(key, key);*/

            // redis 版
            if (redisTemplate.hasKey(key)) {
                throw new RuntimeException("请勿重复请求");
            }
            redisTemplate.opsForValue().set(key, null, 5, TimeUnit.SECONDS);
        }
        try {
            return pjp.proceed();
        } catch (Throwable throwable) {
            throw new RuntimeException("服务器异常");
        } finally {
            // TODO 为了演示效果,这里就不调用 CACHES.invalidate(key); 代码了
        }
    }

    /**
     * key 的生成策略,如果想灵活可以写成接口与实现类的方式（TODO 后续讲解）
     *
     * @param keyExpress 表达式
     * @param args       参数
     * @return 生成的key
     */
    private String getKey(String keyExpress, Object[] args) {
        System.out.println("httpServletRequest" + httpServletRequest.getClass());
        String ip = IpUtil.getIpAddr(httpServletRequest);
        StringBuffer sb = new StringBuffer(ip);
        for (int i = 0; i < args.length; i++) {
            keyExpress = keyExpress.replace("arg[" + i + "]", args[i].toString());
            sb.append(args[i].toString());
        }

        String md5Str = DigestUtils.md5DigestAsHex(sb.toString().getBytes(StandardCharsets.UTF_8)).toUpperCase();
        System.out.println("md5Str==================>" + md5Str);
        System.out.println("sb==================>" + sb.toString());
        return keyExpress;
    }

}
